﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Security.Cryptography;
using System.IO;

namespace BlueLotus7.Common.LicenseAndSupport
{
    public class LicenseAndSupportSender
    {
        const string messageReceiverUrl = "http://las.bluelotus7.cn/LisenceAndSupport.aspx?" + Constant.LicenseAndSupport.Key_SenderSelfVersion + "={0}&" + Constant.LicenseAndSupport.Key_TokenCode + "={1}";
        const string licenseAndSupportSenderVersion = "1.0.0.0";
        const string allZero8 = "00000000";
        const string licenseFile = "license.dat";

        /// <summary>
        /// Version of sender.
        /// </summary>
        Version selfVersion;

        /// <summary>
        /// The application name.
        /// </summary>
        string applicationName = string.Empty;

        /// <summary>
        /// The application version.
        /// </summary>
        Version applicationVersion;

        /// <summary>
        /// The license code for the application.
        /// </summary>
        string licenseCode = string.Empty;

        /// <summary>
        /// Encrypt code for file encryption.
        /// </summary>
        string encryptCode;

        /// <summary>
        /// Dynamic encrypt code for network encryption.
        /// </summary>
        string dynamicEncryptCode;

        /// <summary>
        /// Token for network communication.
        /// </summary>
        string token = string.Empty;

        bool isReady = false;

        /// <summary>
        /// Indicates whether authorized.
        /// </summary>
        public bool Authorized
        {
            get { return token != null; }
        }



        public LicenseAndSupportSender(string applicationName, Version applicationVersion)
        {
            this.selfVersion = new Version(licenseAndSupportSenderVersion);
            this.applicationName = applicationName == null ? string.Empty : applicationName;
            this.applicationVersion = applicationVersion == null ? new Version() : applicationVersion;

            if (!GetLicenseCodeFromFile())
            {
                this.dynamicEncryptCode = Generate3DesEncryptKeyByVersion(this.selfVersion);
            }
        }

        /// <summary>
        /// Authorizes to services.
        /// </summary>
        /// <returns></returns>
        public bool AuthorizeService()
        {
            try
            {
                string responseString = SendData(Constant.LicenseAndSupport.Key_Authorize, licenseCode);
                string[] datas = null;
                if (RetrieveDatas(responseString, out datas) && datas != null && datas.Length > 0)
                {
                    string dynamicEncryptCode = datas[0];
                    if (!string.IsNullOrEmpty(dynamicEncryptCode) && Constant.LicenseAndSupport.EncryptKeyLength == dynamicEncryptCode.Length)
                    {
                        this.dynamicEncryptCode = dynamicEncryptCode;
                        return true;
                    }
                }
                return false;
            }
            catch
            {
                return false;
            }
            finally
            {
                isReady = true;
            }
        }

        /// <summary>
        /// Sends exception.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <returns>Indicates whether successfully.</returns>
        public bool SendException(BaseException exception)
        {
            bool result = false;
            if (exception != null)
            {
                string data = AssembleDatas(DateTime.Now.ToString(), exception.GetType().ToString(), exception.Message, exception.StackTrace);
                string responseString = SendData(Constant.LicenseAndSupport.Key_Exception, data);
                if (Constant.LicenseAndSupport.Result_Success == responseString)
                {
                    result = true;
                }
            }
            return result;
        }

        /// <summary>
        /// Sends message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>Indicates whether successfully.</returns>
        public bool SendMessage(string message)
        {
            bool result = false;
            if (!string.IsNullOrEmpty(message))
            {
                string responseString = SendData(Constant.LicenseAndSupport.Key_Message, message);
                if (Constant.LicenseAndSupport.Result_Success == responseString)
                {
                    result = true;
                }
            }
            return result;
        }

        /// <summary>
        /// Checks new verison.
        /// </summary>
        /// <param name="newVersionInfos">The new version infos.</param>
        /// <returns>Indicates whether successfully.</returns>
        public bool CheckNewVersion(out string[] newVersionInfos)
        {
            newVersionInfos = null;
            bool result = false;

            string responseString = SendData(Constant.LicenseAndSupport.Key_CheckNewVersion, licenseCode);

            if (RetrieveDatas(responseString, out newVersionInfos))
            {
                result = true;
            }

            return result;
        }

        /// <summary>
        /// Sends data.
        /// </summary>
        /// <param name="key">The key in "e", "m" or "l".</param>
        /// <param name="data">The data.</param>
        /// <returns>The response message.</returns>
        private string SendData(string key, string data)
        {
            HttpWebRequest request = GenerateHttpRequest();
            Dictionary<string, string> postParameters = new Dictionary<string, string>();
            postParameters.Add(Constant.LicenseAndSupport.Key_ApplicationName, EncodeBase64FromString(this.applicationName));
            postParameters.Add(Constant.LicenseAndSupport.Key_ApplicationVersion, EncodeBase64FromString(this.applicationVersion.ToString()));
            postParameters.Add(key, Encrypt3DES(data, dynamicEncryptCode));
            FillPostDataIntoHttpWebRequest(request, postParameters);
            return SendRequest(request);
        }

        /// <summary>
        /// Sends request.
        /// </summary>
        /// <param name="request">The <c>HttpWebRequest</c> to send.</param>
        /// <returns>The response string.</returns>
        private string SendRequest(HttpWebRequest request)
        {
            return GetTextContentFromWebResponse(request.GetResponse(), Encoding.UTF8);
        }

        /// <summary>
        /// Decodes data
        /// </summary>
        /// <param name="responseMessage">The response message.</param>
        /// <returns>The result string.</returns>
        private string DecodeData(string responseMessage)
        {
            string result = string.Empty;

            if (!string.IsNullOrEmpty(responseMessage))
            {
                try
                {
                    string decryptCode = dynamicEncryptCode == null ? licenseAndSupportSenderVersion : dynamicEncryptCode;
                    result = Decrypt3DES(responseMessage, decryptCode);
                }
                catch { }
            }
            return result;
        }

        /// <summary>
        /// Gets License code from file.
        /// </summary>
        /// <returns>Indicates successful.</returns>
        private bool GetLicenseCodeFromFile()
        {
            try
            {
                string fileContent = File.ReadAllText(licenseFile);
                if (!string.IsNullOrEmpty(fileContent) && fileContent.Length > Constant.LicenseAndSupport.EncryptKeyLength)
                {
                    string encryptCode = fileContent.Substring(0, Constant.LicenseAndSupport.EncryptKeyLength);
                    string decryptedContent = Decrypt3DES(fileContent.Substring(Constant.LicenseAndSupport.EncryptKeyLength), encryptCode);
                    string[] datas = decryptedContent.Split('|');
                    if (datas.Length == 3)
                    {
                        if (!string.IsNullOrEmpty(datas[0]) && datas[0] == this.applicationName)
                        {
                            Version version = new Version(datas[1]);
                            if (version > Constant.LicenseAndSupport.ZeroVersion && version == this.applicationVersion)
                            {
                                this.licenseCode = datas[2];
                                return true;
                            }
                        }
                    }
                }
            }
            catch { }
            return false;
        }

        /// <summary>
        /// Assembles datas
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns>The string assembled.</returns>
        private string AssembleDatas(params string[] parameters)
        {
            StringBuilder sb = new StringBuilder();

            if (parameters != null)
            {
                foreach (string param in parameters)
                {
                    string data = EncodeBase64FromString(param == null ? string.Empty : param);
                    sb.Append(data + "|");
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Retrieves datas.
        /// </summary>
        /// <param name="dataString">The data string.</param>
        /// <param name="datas">The array of data.</param>
        /// <returns>Indicate whether successfully.</returns>
        private bool RetrieveDatas(string dataString, out string[] datas)
        {
            datas = null;
            bool result = false;

            if (dataString != null)
            {
                datas = dataString.Split(new char[] { '|' });
                if (datas.Length > 0)
                {
                    if (datas[0] == Constant.LicenseAndSupport.Result_Success)
                    {
                        result = true;
                    }
                    for (int i = 1; i < datas.Length; i++)
                    {
                        datas[i] = DecodeBase64ToString(datas[i]);
                    }
                }
            }
            return result;
        }

        #region Utility

        #region 3DES

        private static string Encrypt3DES(string content, string encryptKey)
        {
            return Encrypt3DES(content, encryptKey, CipherMode.ECB, Encoding.ASCII);
        }
        private static string Encrypt3DES(string content, string encryptKey, CipherMode cipherMode, Encoding encoding)
        {
            string result = null;

            if (!string.IsNullOrEmpty(content) && !string.IsNullOrEmpty(encryptKey) && encoding != null)
            {
                TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider();

                DES.Key = ASCIIEncoding.ASCII.GetBytes(encryptKey);
                DES.Mode = cipherMode;

                ICryptoTransform DESEncrypt = DES.CreateEncryptor();

                byte[] Buffer = encoding.GetBytes(content);
                result = Convert.ToBase64String(DESEncrypt.TransformFinalBlock(Buffer, 0, Buffer.Length));
            }
            return result;
        }
        private static string Decrypt3DES(string content, string encryptKey)
        {
            return Decrypt3DES(content, encryptKey, CipherMode.ECB, Encoding.ASCII);
        }
        private static string Decrypt3DES(string content, string encryptKey, CipherMode cipherMode, Encoding encoding)
        {
            string result = null;

            if (!string.IsNullOrEmpty(content) && !string.IsNullOrEmpty(encryptKey) && encoding != null)
            {
                TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider();

                DES.Key = ASCIIEncoding.ASCII.GetBytes(encryptKey);
                DES.Mode = cipherMode;
                DES.Padding = System.Security.Cryptography.PaddingMode.PKCS7;

                ICryptoTransform DESDecrypt = DES.CreateDecryptor();

                try
                {
                    byte[] Buffer = Convert.FromBase64String(content);
                    result = encoding.GetString(DESDecrypt.TransformFinalBlock(Buffer, 0, Buffer.Length));
                }
                catch { }
            }

            return result;
        }

        #endregion

        #region Base64

        public string EncodeBase64FromString(string source)
        {
            try
            {
                byte[] bytes = System.Text.Encoding.Default.GetBytes(source);
                return Convert.ToBase64String(bytes);
            }
            catch
            {
                return string.Empty;
            }
        }

        public string EncodeBase64FromBytes(byte[] bytes)
        {
            try
            {
                return Convert.ToBase64String(bytes);
            }
            catch
            {
                return string.Empty;
            }
        }

        public string DecodeBase64ToString(string result)
        {
            try
            {
                byte[] bytes = Convert.FromBase64String(result);
                return System.Text.Encoding.Default.GetString(bytes);
            }
            catch
            {
                return string.Empty;
            }
        }

        public byte[] DecodeBase64ToBytes(string result)
        {
            try
            {
                byte[] bytes = Convert.FromBase64String(result);
                return bytes;
            }
            catch
            {
                return null;
            }
        }

        #endregion

        /// <summary>
        /// Fills post data into <c>HttpWebRequest</c>.
        /// </summary>
        /// <param name="httpWebRequest">The <c>HttpWebRequest</c> instance.</param>
        /// <param name="dataMappings">The data mapping.</param>
        private void FillPostDataIntoHttpWebRequest(HttpWebRequest httpWebRequest, Dictionary<string, string> dataMappings)
        {
            if (httpWebRequest != null)
            {
                StringBuilder sb = new StringBuilder();
                if (dataMappings != null)
                {
                    foreach (string key in dataMappings.Keys)
                    {
                        string value = string.Empty;
                        if (dataMappings[key] != null)
                        {
                            value = dataMappings[key];
                        }
                        sb.Append(key + "=" + value.Trim() + "&");
                    }

                }
                if (sb.Length > 0)
                {
                    sb.Remove(sb.Length - 1, 1);
                }
                ASCIIEncoding encoding = new ASCIIEncoding();
                byte[] data = encoding.GetBytes(sb.ToString());

                httpWebRequest.Method = "POST";
                httpWebRequest.ContentType = "application/x-www-form-urlencoded";
                httpWebRequest.ContentLength = data.Length;
                Stream stream = httpWebRequest.GetRequestStream();
                stream.Write(data, 0, data.Length);
                stream.Close();
            }
        }

        /// <summary>
        /// Gets text content from WebResponse by specified encoding.
        /// </summary>
        /// <param name="webResponse">The WebResponse instance.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns>The content string.</returns>
        private string GetTextContentFromWebResponse(WebResponse webResponse, Encoding encoding)
        {
            string textContent = string.Empty;

            if (encoding == null)
            {
                encoding = Encoding.Default;
            }

            if (webResponse != null)
            {
                Stream stream = webResponse.GetResponseStream();
                StreamReader reader = new StreamReader(stream, encoding);
                textContent = reader.ReadToEnd();
            }

            return textContent;
        }

        /// <summary>
        /// Generates <c>HttpWebRequest</c> instance to send.
        /// </summary>
        /// <returns>The <c>HttpWebRequest</c> instance.</returns>
        private HttpWebRequest GenerateHttpRequest()
        {
            string url = string.Format(messageReceiverUrl, licenseAndSupportSenderVersion, this.token);
            return (HttpWebRequest)HttpWebRequest.Create(url);
        }

        /// <summary>
        /// Generates 3Des encrypt key by <c>Version</c>.
        /// </summary>
        /// <param name="version">The <c>Version</c>.</param>
        /// <returns>The encryptKey.</returns>
        private string Generate3DesEncryptKeyByVersion(Version version)
        {
            string baseCode = version.Major.ToString(allZero8) + version.Minor.ToString(allZero8) + version.Build.ToString(allZero8) + version.Revision.ToString(allZero8);
            return baseCode + baseCode;
        }

        #endregion
    }
}
